package com.ctakit.kiwi.common.core.object;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.annotation.JSONField;
import com.ctakit.kiwi.common.core.constant.ErrorCodeEnum;
import com.ctakit.kiwi.common.core.util.ContextUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 接口返回对象
 *
 * @author lizhenmin
 * @date 2020-09-24
 */
@Slf4j
@Data
public class ResponseResult<T> {

    /**
     * 为了优化性能，所有没有携带数据的正确结果，均可用该对象表示。
     */
    private static final ResponseResult<Void> OK = new ResponseResult<>();
    /**
     * 是否成功标记。
     */
    private boolean success = true;
    /**
     * 错误码。
     */
    private String code = "NO-ERROR";
    /**
     * 错误信息描述。
     */
    private String message = "NO-MESSAGE";
    /**
     * 实际数据。
     */
    private T data = null;

    @JSONField(serializeFeatures = {JSONWriter.Feature.NotWriteDefaultValue})
    protected Object requestArgs;

    /**
     * 根据参数codeEnum的枚举值，判断创建成功对象还是错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 codeEnum 的 name() 和 getMessage()。
     *
     * @param codeEnum 错误码枚举
     * @return 返回创建的ResponseResult实例对象
     */
    public static ResponseResult<Void> create(ErrorCodeEnum codeEnum) {
        return create(codeEnum, codeEnum.getMessage());
    }

    /**
     * 根据参数codeEnum的枚举值，判断创建成功对象还是错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 codeEnum 的 name() 和参数 message。
     *
     * @param codeEnum 错误码枚举。
     * @param message  如果该参数为null，错误信息取自codeEnum参数内置的message，否则使用当前参数。
     * @return 返回创建的ResponseResult实例对象
     */
    public static ResponseResult<Void> create(ErrorCodeEnum codeEnum, String message) {
        message = message != null ? message : codeEnum.getMessage();
        return codeEnum == ErrorCodeEnum.NO_ERROR ? success() : error(codeEnum.name(), message);
    }

    /**
     * 根据参数code是否为空，判断创建成功对象还是错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 code 和参数 message。
     *
     * @param code    自定义的错误码
     * @param message 自定义的错误信息
     * @return 返回创建的ResponseResult实例对象
     */
    public static ResponseResult<Void> create(String code, String message) {
        return code == null ? success() : error(code, message);
    }

    /**
     * 根据参数codeEnum的枚举值，判断创建成功对象还是错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 codeEnum 的 name() 和参数 message。
     *
     * @param codeEnum 错误码枚举。
     * @param message  如果该参数为null，错误信息取自codeEnum参数内置的message，否则使用当前参数。
     * @param data     如果错误枚举值为NO_ERROR，则返回该数据。
     * @return 返回创建的ResponseResult实例对象
     */
    public static <T> ResponseResult<T> create(ErrorCodeEnum codeEnum, String message, T data) {
        message = message != null ? message : codeEnum.getMessage();
        return codeEnum == ErrorCodeEnum.NO_ERROR ? success(data) : error(codeEnum.name(), message);
    }

    /**
     * 创建成功对象。
     * 如果需要绑定返回数据，可以在实例化后调用setDataObject方法。
     *
     * @return 返回创建的ResponseResult实例对象
     */
    public static ResponseResult<Void> success() {
        return OK;
    }

    /**
     * 创建带有返回数据的成功对象。
     *
     * @param data 返回的数据对象
     * @return 返回创建的ResponseResult实例对象
     */
    public static <T> ResponseResult<T> success(T data) {
        ResponseResult<T> resp = new ResponseResult<>();
        resp.data = data;
        return resp;
    }

    /**
     * 创建错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 codeEnum 的 name() 和 getMessage()。
     *
     * @param codeEnum 错误码枚举
     * @return 返回创建的ResponseResult实例对象
     */
    public static <T> ResponseResult<T> error(ErrorCodeEnum codeEnum) {
        return error(codeEnum.name(), codeEnum.getMessage());
    }

    /**
     * 创建错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 codeEnum 的 name() 和参数 message。
     *
     * @param codeEnum 错误码枚举
     * @param message  自定义的错误信息
     * @return 返回创建的ResponseResult实例对象
     */
    public static <T> ResponseResult<T> error(ErrorCodeEnum codeEnum, String message) {
        return error(codeEnum.name(), message);
    }

    /**
     * 创建错误对象。
     * 如果返回错误对象，code 和 message 分别取自于参数 code 和参数 message。
     *
     * @param code    自定义的错误码
     * @param message 自定义的错误信息
     * @return 返回创建的ResponseResult实例对象
     */
    public static <T> ResponseResult<T> error(String code, String message) {
        return new ResponseResult<>(code, message);
    }

    /**
     * 根据参数中出错的ResponseResult，创建新的错误应答对象。
     *
     * @param errorCause 导致错误原因的应答对象。
     * @return 返回创建的ResponseResult实例对象。
     */
    public static <T, E> ResponseResult<T> errorFrom(ResponseResult<E> errorCause) {
        return error(errorCause.code, errorCause.getMessage());
    }

    /**
     * 根据参数中出错的CallResult，创建新的错误应答对象。
     *
     * @param errorCause 导致错误原因的应答对象。
     * @return 返回创建的ResponseResult实例对象。
     */
    public static <T> ResponseResult<T> errorFrom(CallResult errorCause) {
        return error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorCause.getMessage());
    }

    /**
     * 是否成功。
     *
     * @return true成功，否则false。
     */
    public boolean isSuccess() {
        return success;
    }

    /**
     * 通过HttpServletResponse直接输出应该信息的工具方法。
     *
     * @param httpStatus     http状态码。
     * @param responseResult 应答内容。
     * @param <T>            数据对象类型。
     * @throws IOException 异常错误。
     */
    public static <T> void output(int httpStatus, ResponseResult<T> responseResult) throws IOException {
        if (httpStatus != HttpServletResponse.SC_OK) {
            log.error(JSONUtil.toJsonStr(responseResult));
        } else {
            if (log.isDebugEnabled()) {
                log.debug(JSONUtil.toJsonStr(responseResult));
            }


        }
        HttpServletResponse response = ContextUtil.getHttpResponse();
        PrintWriter out = response.getWriter();
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(httpStatus);
        if (responseResult != null) {
            out.print(JSONUtil.toJsonStr(responseResult));
        }
        out.flush();
    }

    /**
     * 通过HttpServletResponse直接输出应该信息的工具方法。
     *
     * @param httpStatus http状态码。
     * @param <T>        数据对象类型。
     * @throws IOException 异常错误。
     */
    public static <T> void output(int httpStatus) throws IOException {
        output(httpStatus, null);
    }

    /**
     * 通过HttpServletResponse直接输出应该信息的工具方法。Http状态码为200。
     *
     * @param responseResult 应答内容。
     * @param <T>            数据对象类型。
     * @throws IOException 异常错误。
     */
    public static <T> void output(ResponseResult<T> responseResult) throws IOException {
        output(HttpServletResponse.SC_OK, responseResult);
    }

    private ResponseResult() {

    }

    private ResponseResult(String code, String message) {
        this.success = false;
        this.code = code;
        this.message = message;
    }
}
